/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is dual-licensed under either the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree or the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree. You may select, at your option, one of the
 * above-listed licenses.
 */

use buck2_core::is_open_source;

use crate::interface::HealthCheckType;

/// Severity of the issue reported by a health check.
#[derive(Clone, PartialEq)]
pub enum Severity {
    Info,
    Warning,
}

/// Remediation for an issue reported by a health check.
#[derive(Clone, PartialEq)]
pub enum Remediation {
    /// A message to display to the user.
    Message(String),
    /// A hyperlink to a page containing more details and remediation.
    Link(String),
}

/// Format options for health issue messages.
#[derive(Clone, Debug, PartialEq)]
pub enum Message {
    /// Simple text message.
    Simple(String),
    /// Rich format with header, body, and footer.
    Rich {
        header: String,
        body: String,
        footer: Option<String>,
    },
}

/// Report generated by a health check.
#[derive(Clone, PartialEq)]
pub struct Report {
    /// Display report to be shown to the user. Some health checks may only log tags.
    pub display_report: Option<DisplayReport>,

    /// Tags to be logged to scuba.
    pub tag: Option<String>,
}

/// Report to be displayed to the user on the console.
#[derive(Clone, PartialEq)]
pub struct DisplayReport {
    /// The health check that generated this report.
    pub health_check_type: HealthCheckType,

    /// An optional warning to display to the user.
    /// When a health check runs but has nothing to report, this field should be None.
    pub health_issue: Option<HealthIssue>,
}

#[derive(Clone, PartialEq)]
pub struct HealthIssue {
    /// The severity of the issue reported by the health check.
    pub severity: Severity,

    /// The message format and content to display to the user.
    pub message: Message,

    /// The remediation for the issue reported by the health check.
    /// This is optional because some health checks may not have a remediation.
    pub remediation: Option<Remediation>,
}

impl std::fmt::Display for Message {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Message::Simple(text) => write!(f, "{}", text),
            Message::Rich {
                header,
                body,
                footer,
            } => {
                write!(f, "{}\n{}", header, body)?;
                if let Some(footer_text) = footer {
                    write!(f, "\n{}", footer_text)?;
                }
                Ok(())
            }
        }
    }
}

impl std::fmt::Display for HealthIssue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let remediation = if is_open_source() {
            String::new()
        } else {
            match &self.remediation {
                Some(Remediation::Message(message)) => {
                    format!(". {message}")
                }
                Some(Remediation::Link(link)) => {
                    format!(". Refer to {link}")
                }
                None => String::new(),
            }
        };
        write!(f, "{}{}", self.message, remediation)
    }
}

#[cfg(fbcode_build)]
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_display_report_with_message_remediation() {
        let report = HealthIssue {
            severity: Severity::Warning,
            message: Message::Simple("This is a warning".to_owned()),
            remediation: Some(Remediation::Message("Fix this by doing X".to_owned())),
        };

        assert_eq!(report.to_string(), "This is a warning. Fix this by doing X");
    }

    #[test]
    fn test_display_report_with_link_remediation() {
        let report = HealthIssue {
            severity: Severity::Info,
            message: Message::Simple("Information message".to_owned()),
            remediation: Some(Remediation::Link("https://example.com/help".to_owned())),
        };

        assert_eq!(
            report.to_string(),
            "Information message. Refer to https://example.com/help"
        );
    }

    #[test]
    fn test_display_report_without_remediation() {
        let report = HealthIssue {
            severity: Severity::Warning,
            message: Message::Simple("Just a message".to_owned()),
            remediation: None,
        };

        assert_eq!(report.to_string(), "Just a message");
    }

    #[test]
    fn test_display_rich_message_format() {
        let report = HealthIssue {
            severity: Severity::Warning,
            message: Message::Rich {
                header: "Build Warning".to_owned(),
                body: "Your build is taking longer than expected.".to_owned(),
                footer: Some("Consider using buildmate for analysis.".to_owned()),
            },
            remediation: None,
        };

        assert_eq!(
            report.to_string(),
            "Build Warning\nYour build is taking longer than expected.\nConsider using buildmate for analysis."
        );
    }

    #[test]
    fn test_display_rich_message_format_without_footer() {
        let report = HealthIssue {
            severity: Severity::Info,
            message: Message::Rich {
                header: "Information".to_owned(),
                body: "This is the main message.".to_owned(),
                footer: None,
            },
            remediation: None,
        };

        assert_eq!(report.to_string(), "Information\nThis is the main message.");
    }
}
